package com.hb.service;

import com.hb.domain.Spitter;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;

import com.hb.domain.Spittle;
import org.springframework.security.access.prepost.PreFilter;

import java.util.Date;
import java.util.List;

public class ExpressionSecuredSpittleService implements SpittleService {
    @Override
    // @PreAuthorize 的 String 类型参数是一个 SpEL 表达式。借助于 SpEL 表达式来实现访问决策，我们能够编写出更高级的安全性约束。
    // 例如，Spittr 应用程序的一般用户只能写 140 个字以内的 Spittle，而付费用户不限制字数。表达式中的 #spittle 部分直接引用了方法中的同名参数。这使得
    // Spring Security 能够检查传入方法的参数，并将这些参数用于认证决策的制定。
    @PreAuthorize("(hasRole('ROLE_SPITTER') and #spittle.text.length() le 140) or hasRole('ROLE_PREMIUM')")
    public void addSpittle(Spittle spittle) {
        System.out.println("Method was called successfully");
    }

    /**
     * 例如，假设我们想对 getSpittleById() 方法进行保护，确保返回的 Spittle 对象属于当前的认证用户。我们只有得到 Spittle 对象之后，才能判断它是否属于当前用户。
     * 因此，getSpittleById() 方法必须要先执行。在得到 Spittle 之后，如果它不属于当前用户的话，将会抛出安全性异常。可以按照如下的方式使用 @PostAuthorize 注解
     * 完成这一需求。为了便利地访问受保护方法的返回对象，Spring Security 在 SpEL 中提供了名为 returnObject 的变量。在这里，我们知道返回对象是一个 Spittle 对象，
     * 所以这个表达式可以直接访问其 spittle 属性中的 username 属性。在对比表达式双等号的另一侧，表达式到内置的 principal 对象中取出其 username 属性。principal
     * 是另一个 Spring Security 内置的特殊名称，它代表了当前认证用户的主要信息（通常是用户名）。在 Spittle 对象所包含 Spitter 中，如果 username 属性与 principal
     * 的 username 属性相同，这个 Spittle 将返回给调用者。否则，会抛出一个 AccessDeniedException 异常，而调用者也不会得到 Spittle 对象。
     * @param id
     * @return
     */
    @PostAuthorize("returnObject.spitter.username == principal.username")
    public Spittle getSpittleById(long id) {
        if (id == 1) {
            return new Spittle(
                    new Long(1),
                    new Spitter(new Long(1), "hb", "123456", "胡斌", "981145308@qq.com", true),
                    "Spittle text",
                    new Date());
        } else {
            return new Spittle(
                    new Long(2),
                    new Spitter(new Long(2), "dm", "123456", "李伟", "123456789@qq.com", true),
                    "Spittle text 111",
                    new Date(2022, 1, 1));
        }
    }

    /**
     * 例如，我们有一个名为 getOffensiveSpittles() 的方法，这个方法会返回标记为具有攻击性的 Spittle 列表。这个方法主要会给管理员使用，以保证 Spittr 应用中内容的和谐。
     * 但是，普通用户也可以使用这个方法，用来查看他们所发布的 Spittle 有没有被标记为具有攻击性。按照这种方法的定义，getOffensiveSpittles() 方法与具体的用户并没有关联。
     * 它只会返回攻击性 Spittle 的一个列表，并不关心它们属于哪个用户。对于管理员使用来说，这是一个很好的方法，但是它无法限制列表中的 Spittle 都属于当前用户。当然，我们也
     * 可以重载 getOffensiveSpittles()，实现另一个版本，让它接受一个用户 ID 作为参数，查询给定用户的 Spittle。但是，始终会有这样的可能性，那就是将较为宽松限制的版本用
     * 在具有一定安全限制的场景中。我们需要有一种方式过滤 getOffensiveSpittles() 方法返回的 Spittle 集合，将结果限制为允许当前用户看到的内容，而这就是 Spring Security
     * 的 @PostFilter 所能做的事情。
     * @return
     */
    @PreAuthorize("hasAnyRole({'ROLE_SPITTER', 'ROLE_ADMIN'})")
    // @PostFilter 注解将会过滤这个列表，确保用户只能看到允许的 Spittle。具体来讲，管理员能够看到所有攻击性的 Spittle，非管理员只能看到属于自己的 Spittle。
    // 表达式中的 filterObject 对象引用的是这个方法所返回 List 中的某一个元素（我们知道它是一个 Spittle）。在这个 Spittle 对象中，如果 Spitter 的用户名与
    // 认证用户（表达式中的 principal.name）相同或者用户具有 ROLE_ADMIN 角色，那这个元素将会最终包含在过滤后的列表中。否则，它将被过滤掉。
    @PostFilter("hasRole('ROLE_ADMIN') || filterObject.spitter.username == principal.username")
    public List<Spittle> getOffensiceSpittles() {
        return null;
    }

    /**
     * 例如，假设我们希望以批处理的方式删除 Spittle 组成的列表。如果我们想在它上面应用一些安全规则的话，比如 Spittle 只能由其所有者或管理员删除，那该怎么做呢？
     * 如果是这样的话，我们可以将逻辑放在 deleteSpittles() 方法中，在这里循环列表中的 Spittle，只删除属于当前用户的那一部分对象（如果当前用户是管理员的话，则会全部删除）。
     * Spring Security 的 @PreFilter 注解能够很好地解决这个问题。与 @PostFilter 非常类似，@PreFilter 也使用 SpEL 来过滤集合，只有满足 SpEL 表达式的元素才会留在
     * 集合中。但是它所过滤的不是方法的返回值，@PreFilter 过滤的是要进入方法中的集合成员。
     * @param spittles
     */
    @PreAuthorize("hasAnyRole({'ROLE_SPITTER', 'ROLE_ADMIN'})")
    // @PreFilter 注解能够保证传递给 deleteSpittles() 方法的列表中，只包含当前用户有权限删除的 Spittle。这个表达式会针对集合中的每个元素进行计算，
    // 只有表达式计算结果为 true 的元素才会保留在列表中。targetObject 是 Spring Security 提供的另外一个值，它代表了要进行计算的当前列表元素。
    @PreFilter("hasRole('ROLE_ADMIN') || targetObject.spitter.username == principal.username")
    public void deleteSpittles(List<Spittle> spittles) { }

    @PreAuthorize("hasAnyRole({'ROLE_SPITTER', 'ROLE_ADMIN'})")
    // 相比上面，现在的 @PreFilter 表达式更加紧凑。它实际上只是在问一个问题“用户有权限删除目标对象吗？”。如果有的话，表达式的计算结果为 true，Spittle 会保存在列表中，
    // 并传递给 deleteSpittles() 方法。如果没有权限的话，它将会被移除掉。hasPermission() 函数是 Spring Security 为 SpEL 提供的扩展，它为开发者提供了一个时机，
    // 能够在执行计算的时候插入任意的逻辑。我们所需要做的就是编写并注册一个自定义的许可计算器。
    @PreFilter("hasPermission(targetObject, 'delete')")
    public void deleteSpittles1(List<Spittle> spittles) {}
}
